package com.geezdata.cps.base.aop;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;

import com.geezdata.cps.base.annotation.Retry;


@Aspect
public class RetryInterceptor {
    private static Logger logger = LoggerFactory.getLogger(RetryInterceptor.class);

    /**
     * Aspect输出日志
     *
     * @param mi
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.geezdata.cps.base.annotation.Retry)")
    public Object retry(ProceedingJoinPoint mi) throws Throwable {
        ProxyMethodInvocation method =
                (ProxyMethodInvocation) FieldUtils.readDeclaredField((MethodInvocationProceedingJoinPoint) mi,
                        "methodInvocation", true);

        // 调用AOP方法拦截输出
        return this.invoke(method);
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        // 整理调用方法的信息;
        String methodSimpleInfo = getMethodSimpleName(mi);

        // 判断Annotation重试次数
        Retry retry = mi.getMethod().getAnnotation(Retry.class);
        int retryTimes = retry.times();
        Class<? extends Throwable>[] expected = retry.expected();

        while (true) {
            retryTimes--;

            try {
                return mi.proceed();
            } catch (Throwable t) {
                if (!isExpected(expected, t.getClass()) || retryTimes <= 0) {
                    logger.error("Call {} failed {} times.", methodSimpleInfo, retry.times());
                    throw t;
                }
            }
        }
    }

    private boolean isExpected(Class<? extends Throwable>[] expected, Class<? extends Throwable> target) {
        for (Class<? extends Throwable> clazz : expected) {
            if (clazz.isAssignableFrom(target)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 取得方法名称
     *
     * @param mi
     * @return
     */
    private static String getMethodSimpleName(MethodInvocation mi) {
        return new StringBuilder(ClassUtils.getSimpleName(mi.getThis().getClass())).append(".")
                .append(mi.getMethod().getName()).toString();
    }
}
